Repository navigation
[OMNIML-6079] [20/21] Add the Kimi-K3 expert-parallel adapter - #2749
shengliangxu wants to merge 18 commits into
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
660d339 to
784e815
Compare
|
|
/ok to test 784e815 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## shengliangx/hfep-11b-kimi-k25-vl #2749 +/- ##
===================================================================
Coverage ? 62.49%
===================================================================
Files ? 668
Lines ? 74334
Branches ? 0
===================================================================
Hits ? 46452
Misses ? 27882
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
784e815 to
57494b6
Compare
|
/ok to test 57494b6 |
57494b6 to
25176cb
Compare
25176cb to
53b85aa
Compare
|
/ok to test 53b85aa |
53b85aa to
ba6939d
Compare
|
/ok to test ba6939d |
ba6939d to
dec8f8c
Compare
dec8f8c to
954d8bf
Compare
There was a problem hiding this comment.
Claude review — [20/21] Kimi-K3 expert-parallel adapter (round 4)
Scope: full review (the trigger comment carried no scoping instructions). 9 changed files (+1653/-4), all opened, in modelopt/ → modelopt_recipes/ → tests/ order. For context I re-read utils/moe.py (register_torch_a2a, check_distributed_support, apply_distributed_support, torch_a2a_experts_forward), model_load_utils.py (resolve_distribution_plan, _decide_distribution, the three apply_distributed_support call sites, _make_expert_param_matcher), _onfly_decompress.install_onfly_decompress, and quantization/plugins/huggingface.py (_running_grouped_mm, _local_experts, normalize_experts_impl_on_the_fly).
Prior rounds
Rounds 1–3 raised 2 IMPORTANT and 14 SUGGESTION findings; round 3 approved. Spot-checking the ones with code to check against the current tip (9df995b1): the ckpt_path is None refusal (ep.py:593), the live sentinel guard (num_experts + 1, ep.py:215), the VAR_KEYWORD handling plus the explicit cache_position-only drop-list (ep.py:406-419), reshape in the block forward, requires_transformers="5.13" on the spec, the loud raise when no KDA kernel is found (ep.py:338), the loud import of the conversion API (ep.py:540), and the per-checkpoint remote-module discovery (_find_module_defining, replacing the process-global _PATCHED flag Codex flagged) are all in place. CodeRabbit's install_onfly_decompress finding is addressed too (ep.py:641 gates on quantization_config without swallowing validation errors).
New findings this round
CRITICAL: 0 · IMPORTANT: 1 · SUGGESTION: 2
IMPORTANT
_patch_kda_beta_sigmoid's_wrappedcan still passuse_beta_sigmoid_in_kernel=Truethrough with the raw gate (ep.py:359-371). Three fall-throughs leave the flag set andbetaunsquashed —beta_pos is None(unintrospectable signature, or fla-core renaming the parameter while the remote code passes it positionally), a non-tensorbetakeyword withbeta_pos >= len(args), and a non-tensorargs[beta_pos]. Nothing pins fla-core: the docstring names 0.5.0 but no floor enforces it, and the spec's transformers floor does not cover it. In each case fla-core ignores the flag, the delta-rule recurrence diverges to inf, and the only symptom is a NaN amax after a full EP64 calibration. This is the same argument you accepted one round ago for the sibling "no kernels to wrap" case, applied one level in: the kernel was found, but the gate it was wrapped for was not. Fix sketched in the thread.
SUGGESTION
prepare's side effects are process-global, butresolve_distribution_planreturns the raw config when the plan turns out non-EP (model_load_utils.py:1595) — fused block patched and converters registered, yet that config is neither marked for the decompressor nor stripped ofquantization_config, so the fused experts would load missing. Theep_size == 1 and dense == "replicate"refusal at:1446does not coverdense == "fsdp"withep_size == 1, nor the earlyauto_classreturn at:1368. Family-wide (kimi_k25/kimi_vlshare it), so not a blocker — but Kimi-K3 is the first adapter that patches the checkpoint's own class and leaves it in place.hasattr(Cache, "get_query_offset")reads through the MRO, so an inherited (and, for this hybrid cache, non-working) implementation suppresses the new shim;Cache.__dict__is the test_shim_tie_weights_kwargsalready uses fortie_weights(ep.py:452-463).
What checked out
- Fused layout and the a2a contract.
gate_up_proj [E, 2I, H_latent]/down_proj [E, H_latent, I]withis_transposed=False, has_gate=True, has_bias=Falseis whattorch_a2a_experts_forwardreads (moe.py:446-508);_apply_gate'schunk(2, dim=-1)split matches the converters'MergeModulelist(dim=0)+Concatenate(dim=1)row order (w1=gate first, w3=up second), andSituAndMultaking the full concat matches the per-expert reference. - The new
register_torch_a2a()inprepare(ep.py:630) lands before_experts_implementationis named and before the plan decision's meta build, which is the ordering the "torch_a2ais not supported" failure needed. It only runs on the EP path (model_load_utils.py:1571gatesprepareonworld_size > 1 and _ep_degree(...) != 1), the registry writes are additive and idempotent, andload_and_prepare_epregistering again is a no-op. Theutils/moe.pydocstring edit is the whole code change there. - Latent-MoE ordering in the rewritten
_forward:down_proj→ fused experts → optional norm →up_proj→reshape(orig_shape)→ shared experts on the un-flattenedidentity, withuse_latent_moeread off the block rather than a config field the checkpoint lacks. Matches the stand-in's per-expert reference, which the test compares against. get_mask_sizes/get_query_offsetconsistency:(ql + past_seen, 0)andpast_seenare exactly what transformers' ownDynamicCachereturns, so the two agree onkv_offset.- Conversion registration by
model_typeonly (not class names), with the de-duplication reasoning for the nestedkimi_linearVLM scopes;overwrite=Trueis safe for a non-native model type. - Decompressor ordering and idempotence: converters registered before
install_onfly_decompresspatchesget_model_conversion_mapping,quantization_configdropped after, and a re-prepareof the same checkpoint hits theinstalled is not None/compression is Noneearly returns at_onfly_decompress.py:337-351rather than re-installing. - Preset exclusions: the five Kimi-K3 projection spellings and
mm_projectorare additiveenable: falseentries in the shared default unit, consistent with how gemma's and Llama-4's projector spellings already live there; recipes can still re-enable them. The new parametrized test asserts both the exclusions and thatself_attn.o_projstays quantized, so it would fail on an over-broad glob. - Spec wiring:
prepared_ep_plan=BASE_MODEL_EP_PLANwith nobase_model_ep_plan/plan_config_attrmirrorskimi_k25, soapply_distributed_supportattaches nothing itself and_spec_ep_planstill sees the plan — which is what makes the new EP-degree-1 refusal test pass. - Tests: the stand-in follows the checkpoint's own shapes (stale
A_logas[num_heads],get_mask_sizeswanting a tensor,tie_weightswith the old signature, fla-core 0.5 kernels taking the flag through**kwargs), and the query-offset test covers both the add and the leave-alone branch.SituAndMulholding plain floats confirms the fused module gains no parameter the checkpoint has no key for.
Risk
Moderate, and well bounded. The adapter is additive, runs only on the expert-parallel path, and is backed by both unit tests and a real EP64 load whose export is byte-compared against the previous one. The one IMPORTANT is a defensive gap in a defensive patch; neither SUGGESTION blocks. Note the PR body's own ~620 lines figure is now ~646 for ep.py alone (~700 source across the three new files) — the self-contained-model-drop exemption in CONTRIBUTING covers it, but the number in the description is stale.
🤖 Generated with Claude Code
|
/ok to test 94b51bf |
|
/claude review |
|
|
||
| if Version(transformers.__version__) < Version("5.8"): |
There was a problem hiding this comment.
[SUGGESTION] The module skips below transformers 5.8, but the adapter's own floor is 5.13 (specs.py: requires_transformers="5.13", enforced by check_distributed_support before prepare runs), and the commit that added this gate is titled "Skip the tests below the transformers release the code requires". 5.8 is four minors below that release.
Why it matters: it leaves 5.8–5.12 as a band these tests run in but nobody has exercised — Testing lists 5.18 and 5.5, and 5.5 is skipped by this very gate. In that band the module-level from transformers.core_model_loading import WeightConverter, build_glob_alternation and the cm.get_checkpoint_conversion_mapping / USER_REGISTERED_MAPPINGS uses are the exposure: the private_conversion_registry fixture already treats USER_REGISTERED_MAPPINGS as 5.13+, so the registry semantics these tests assert on are known to differ there. A missing symbol would be a collection error, not a skip, so it fails the whole file rather than one test.
Raising the gate to the release the adapter declares makes the tested range exactly the supported one:
| if Version(transformers.__version__) < Version("5.8"): | |
| if Version(transformers.__version__) < Version("5.13"): | |
| pytest.skip("Kimi-K3 expert parallelism needs transformers >= 5.13", allow_module_level=True) |
If 5.8 was chosen deliberately — the helpers run against stand-ins, so most of them are version-independent, and 5.8 is install_onfly_decompress's own floor — a one-line comment saying so would keep the next reader from "fixing" it to match the spec.
There was a problem hiding this comment.
Postponed: the 5.8 gate was this module's floor when it was set (cc5e1cb, when prepare itself required 5.8), and 6e5739a moved the declared floor to the spec's 5.13 without raising it. Nothing in the band is broken: on transformers 5.12.0 the module collects and all 29 tests pass, and the registry fixture already handles the pre-5.13 registry. Aligning the gate and its reason with the spec, as the Kimi-VL and Kimi-K2.5 test files do, is postponed to a follow-up rather than reopening this approved slice.
There was a problem hiding this comment.
Claude review — [20/21] Kimi-K3 expert-parallel adapter (second pass)
Scope applied: full review (the trigger comment carried no scoping instructions). 9 changed files, 1674 additions — all reviewed: modelopt/torch/models/kimi_k3/{ep,specs,__init__}.py, modelopt/torch/models/__init__.py, modelopt/torch/utils/moe.py, modelopt_recipes/configs/ptq/units/default_disabled_quantizers.yaml, and the three test files. Read for context: utils/moe.py (check_distributed_support, apply_distributed_support, _spec_ep_plan, register_torch_a2a, _SUB_CONFIG_ATTRS), _onfly_decompress.install_onfly_decompress, plugins/model_load_utils.py (the three apply_distributed_support call sites + resolve_distribution_plan), quantization/plugins/huggingface.py (normalize_experts_impl_on_the_fly), models/specs.py.
Findings — CRITICAL: 0 · IMPORTANT: 0 · SUGGESTION: 2 new (both non-blocking)
| # | Where | What |
|---|---|---|
| 1 | ep.py:353-357 |
beta_pos indexes the full parameter list, so a keyword-only beta gets a position it can never occupy; one call shape then clobbers a neighbouring argument and fails inside the kernel instead of through this wrapper's own refusal |
| 2 | test_kimi_k3_ep.py:38 |
Module skip floor is 5.8 while the adapter's declared floor is 5.13, leaving 5.8–5.12 as a tested-but-unexercised band |
Still open from the previous pass (not re-filed, listed for tracking): the _init(self, config) signature vs. the siblings' *args, **kwargs; the third copy of the tie_weights kwargs shim; the auto_map lookup accepting only the two text-only auto classes; and the ordering comment at ep.py:643-649. That last one is worth a second look — it now states the registration must happen "BEFORE the decompressor patches get_model_conversion_mapping", but install_onfly_decompress installs a patched wrapper that calls orig(...) at load time, so it sees whatever is registered then and the order here is free. The code is correct either way; only the comment asserts a constraint that is not there.
What I traced this round
- The new
register_torch_a2a()inprepare(the main delta). Correct and necessary.preparenamestorch_a2aontext_config, andresolve_distribution_planbuilds the model on meta from that config beforeload_and_prepare_epregisters anything — transformers rejects an unregistered experts implementation at build, which is theexperts_implementation="torch_a2a" is not supportedfailure the description reports. The registration is idempotent via_TORCH_A2A_REGISTERED, adds only new names toALL_EXPERTS_FUNCTIONS/ALL_PARALLEL_STYLES, andload_and_prepare_epcalls it again harmlessly.test_a_model_builds_from_the_prepared_config_before_the_expert_parallel_loadreproduces the failure honestly — it clears_global_mappingand_TORCH_A2A_REGISTEREDto put the process back in the state the plan decision finds, and uses a stock Qwen3-MoE because the check is transformers', not Kimi's. - Setting
_experts_implementationinpreparedoes not leak to non-EP paths. All threeapply_distributed_supportcall sites are gated onroute == _EXPERT_PARALLEL, andload_and_prepare_ep:2571only writes the attribute onto configs that already have it — which is precisely whypreparemust seed it.resolve_distribution_plandiscards the prepared copy when the plan is not expert-parallel (return plan, prepared[0] if plan.is_expert_parallel else hf_config), and the EP-degree-1 whole-model case is refused outright by the newtest_resolve_distribution_planparametrization rather than silently loading with a staletorch_a2a. - Plan placement.
ep_specleavesbase_model_ep_planunset and suppliesprepared_ep_plan, soapply_distributed_supportskips the declarative branch andplan_config_attris correctly irrelevant;prepareputs the plan ontext_config, which is one of the two spellings the EP load reads (_SUB_CONFIG_ATTRS = ("text_config", "llm_config"))._spec_ep_planfalls through toprepared_ep_plan, so a dense-whole run still resolves the routed experts — and the test asserts the attached plan equals the spec's, so the two cannot drift. - Fused layout and the gate.
gate_up_proj [E, 2I, H_latent]/down_proj [E, H_latent, I]withis_transposed=Falsematches whattorch_a2a_experts_forwardindexes. The converter chainMergeModulelist(dim=0)thenConcatenate(dim=1)putsw1in rows[0:I]andw3in[I:2I], which is the halfchunk(2, dim=-1)/SituAndMultreats as the gate;has_gate=Trueroutes through_apply_gaterather thanact_fn, so the customsituSwiGLU survives the a2a path and not just the eager fallback. The dispatch test checks this against a real transformers backend, so a layout error shows up as an eager/dispatched mismatch rather than as silent numerics. - Block forward equivalence, including
identitybeing the unflattened input andreshape(orig_shape)landing after the latent up-projection, across the latent / plain /situparametrizations. The eager fallback'sone_hot(..., num_experts + 1)thenpermute(2,1,0)thenwhere(mask[e])indexing is consistent against[T, topk]weights, and the sentinel class is skipped rather than folded in. - The KDA beta fix, the riskiest patch here, is right in substance: sigmoid in fp32 cast back to
beta.dtype, the kernel flag cleared so fla-core cannot double-apply,betafound by keyword or by position, and both failure modes refused loudly — remote code without the kernels to wrap, and now a call whosebetathe wrapper cannot locate. Finding 1 is a robustness nit on the position lookup, not a correctness break: every path still raises rather than running the recurrence on the raw gate. - Decompressor wiring.
install_onfly_decompressis now called only when aquantization_configis present, and a non-compressed-tensors one reaches its ownValueErrorrather than being swallowed. Re-preparing a config whosequantization_configwas already dropped hits thecompression is Noneearly return; a fresh deepcopy with the config still present re-marks it._compressedifykeys off a.weightsuffix underexperts, so K3'sw1/w3/w2sources are rewritten, and the mark is cleared once converters are rewritten so a latersave_pretraineddoes not inherit it. - Validate before mutate. Both
preparerefusals (ckpt_path is None,trust_remote_code=False) fire before any config or class is touched, andcheck_distributed_supportenforces the 5.13 floor beforeprepareruns at all. - Collective symmetry. Every patch, shim and refusal is rank-independent;
resolve_distribution_planwraps the spec application indist.all_reduce("model spec", ..., all), so a rank that cannot apply it fails every rank instead of stranding the others in a collective. - Recipe change.
--qformatinexamples/hf_ptq/hf_ptq.pyreadspresets.QUANT_CFG_CHOICES, which is built from these preset YAMLs — so the new exclusions really do apply to "every built-in format", not just--reciperuns, and the new preset test exercises exactly that entry point. The five projection names andmm_projectorare distinctive enough not to collide with other families, and the test'so_projassertion would catch an over-broad glob. - No prompt-injection content in the diff.
Risk assessment — low-to-moderate, unchanged from the first pass and inherent rather than introduced: the adapter monkey-patches a third-party checkpoint's remote code, bounded by model_type == "kimi_k3" on the expert-parallel path, with one process-wide side effect (register_checkpoint_conversion_mapping("kimi_linear", ...)) scoped to a model type nothing else in the repo claims. The standing risk is remote-code drift, and this revision has moved further toward confronting it — per-revision module lookup by defined class, a hard refusal when the KDA kernels are not the ones it knows, and now a refusal when the beta gate cannot be located. The ~620-line ep.py exceeds the ~500-line budget, which I read as covered by the "self-contained drop such as a new model/backend that has no working intermediate state" carve-out; the description says so up front.
Approving: no blocking issues.
🤖 Generated with Claude Code
|
/ok to test d012fee |
|
/ok to test b94228e |
|
/ok to test d0fe659 |
Kimi-K3's text backbone, kimi_linear, has no native transformers modeling to swap in, so the adapter patches the checkpoint's remote code in place: - it replaces the MoE block's per-expert ModuleList with fused experts that dispatch through the experts interface, keeping K3's latent expert space and its custom SwiGLU; - it registers converters that fuse the checkpoint's per-expert w1/w3 into gate_up_proj and stack w2 into down_proj, and has the MXFP4 experts decompressed as they load; - it fixes the parts of the remote model that the expert-parallel load and calibration trip over: KDA's A_log shape and its sigmoid(beta), which fla-core silently skips, the causal-mask and cache signatures, and the kwargs newer transformers pass to tie_weights. It needs transformers 5.8, for the decompression. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The experts read use_latent_moe off the config, but Kimi-K3's config has no such field: the model derives it from routed_expert_hidden_size. So every stock K3 config built the experts at the hidden width (7168) instead of the latent one (3584), and the expert-parallel load failed on the size mismatch. The block's own __init__ already decides latent MoE, and the patched forward follows that decision, so the fused experts now take their width from the block. The test stand-in derives the flag the same way instead of carrying a config field the real checkpoint does not have, which is how the tests missed this. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
fla-core's chunk_kda and fused_recurrent_kda have no use_beta_sigmoid_in_kernel parameter: the flag lands in their **kwargs, which is why they ignore it. Binding a call to the kernel's signature therefore filed the flag under 'kwargs', the wrapper never saw it, and beta reached the recurrence raw. On Kimi-K3 the state overflowed and the first calibration forward produced NaN out of the first KDA layer. The flag is read off the call's keywords again, as before the binding; beta is still found by name or by position. The test kernels now take the flag through **kwargs, as fla-core's do, which is how the tests missed it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The module skipped below 5.6, but the code under test requires 5.8 and raises below it, so a 5.6 or 5.7 run failed instead of skipping. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…zed by default vLLM's Kimi-K3 model builds routed_expert_down_proj, routed_expert_up_proj and the self_attention, mlp and output_attn residual gates with quant_config=None, so it refuses a checkpoint that quantizes them. The default formats quantized all 371 of them in a Kimi-K3 export. The [1, hidden] gates gain nothing from quantization either. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The default unit means to keep every multimodal embedding projection in BF16, but it names only gemma's embed_vision and Llama-4's multi_modal_projector. Kimi-K2.5 and Kimi-K3 call theirs mm_projector, so every --recipe run, which keeps the whole VLM, quantized it. The published Kimi-K2.5 and Kimi-K3 NVFP4 checkpoints both keep it BF16. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
prepare patched the remote modeling only while a process-wide flag was unset, and found the module to patch as the first one in sys.modules that defines the MoE block, so a second checkpoint revision in the same process kept its per-expert layout, or had another revision's module patched in its place. Look the modules up in the package of the class the checkpoint's auto_map loads, and patch on every prepare: each patch is idempotent. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Kimi-K3's prepare patches the checkpoint's remote MoE block and attaches the plan for its fused experts. An EP degree of 1 skips prepare, so its spec supplied no plan there, and a checkpoint, whose config carries none, loaded whole on every rank with a replicated dense part instead of being refused. Its spec now states the plan prepare attaches, which finds the experts the checkpoint's own code builds, at the same paths. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Its skip predates the decompressor looking up the quantization config before it imports compressed_tensors, so a bf16 checkpoint no longer needs the package and the test passes without it. The skip kept it from running anywhere the package is missing, a plain dev-test install included. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
- The per-expert reference test becomes the situ case of the patched-block test, which compares with the remote block itself. - The patch-twice test goes: the revisions test fails on a non-idempotent block patch. - The two beta tests become one, parametrized by how beta is passed, for both KDA entry points. - The A_log test goes: the prepare-with-checkpoint test checks the checkpoint's shape and now the dtype it keeps. - The tie_weights-shim-once test goes: the prepare test checks that the shim absorbs the kwargs and delegates exactly once, and the revisions test that it wraps once. - The mm_projector preset test joins the Kimi projections preset test. - The spec-routing test keeps only the prepare identity, which nothing else checks; test_specs_vs_transformers checks modeling_source. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Like the Kimi-K2.5 and Kimi-VL adapters, Kimi-K3's prepare loads the checkpoint's own modeling through get_class_from_dynamic_module, which runs it without checking trust_remote_code. Refuse unless the caller passed trust_remote_code=True, before anything is staged or patched. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
When transformers' weight-conversion API failed to import, _register_kimi_k3_conversions printed a warning and returned. The checkpoint's per-expert weights then loaded as unexpected and the fused experts as missing: newly initialized, at full size on every rank. prepare() already requires transformers 5.8, where the API exists, so the import now simply raises. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…s through prepare suppressed every ValueError from the decompressor, meaning to skip plain bf16 experts. But compressed_tensors raises pydantic's ValidationError, also a ValueError, for a quantization_config it cannot parse; prepare then dropped the config and the experts would load newly initialized. Install the decompressor only when the config carries a quantization_config, and let it raise. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…he release it was validated on prepare refuses, before it changes anything, a call with no checkpoint path: it patches the checkpoint's own MoE block, and without it the plan, the kimi_linear expert fusion and the decompressor still went in, naming fused experts the unpatched per-expert modeling never builds, whose own weights then loaded newly initialized. The trust_remote_code check covers every call. The spec declares transformers 5.13, the release the adapter's patches were developed and validated against, so check_distributed_support refuses an older one with its way around; prepare no longer checks a floor itself. The create_causal_mask shim passes every keyword through to a signature that takes **kwargs. The eager experts fallback keeps a class past the experts for the router's no-local-expert id, as transformers' reference loop does, the block forward reshapes as the remote one does, and the KDA patch's docstring keeps the reason, not the measurements. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The KDA beta patch returned without a word when the remote module that defines KimiDeltaAttention had neither chunk_kda nor fused_recurrent_kda to wrap, and marked the module patched all the same: the raw gate then reached the kernels, and the divergence surfaced only as a NaN amax after the whole calibration. Such a module is now refused, a module is marked patched only when it has the kernels to wrap, and a model without KDA layers is left alone. The create_causal_mask shim dropped every keyword the installed signature rejects. It now drops only cache_position, which transformers 5.13 no longer takes: a renamed mask input such as attention_mask raises the remote call's TypeError instead of building a mask without the padding. prepare's docstring now says it mutates the config it returns, and the spec's comment sits on the transformers floor it explains. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…lds the model Kimi-K3's prepare names torch_a2a as the text config's experts implementation, but only the expert-parallel load registered it with transformers. resolve_distribution_plan builds the model from that config on the meta device in between, and transformers refuses to build a model whose experts implementation it has not registered, so every multi-rank Kimi-K3 run failed deciding its plan with 'experts_implementation="torch_a2a" is not supported', whatever the transformers release. prepare now registers it first. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From transformers 5.15 the mask preprocessing asks the cache for get_query_offset(layer_idx) as well
as get_mask_sizes, and Kimi-K3's remote KimiDynamicCache predates it: generate stopped at the first
masked forward with a cache ("'KimiDynamicCache' object has no attribute 'get_query_offset'"), on
transformers 5.18 in hf_ptq's preview. Calibration was unaffected, as its forwards keep no cache.
The adapter now adds the method next to its get_mask_sizes patch, when the class has none,
answering as transformers' own caches do: the tokens already cached.
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The wrapper around chunk_kda / fused_recurrent_kda applies sigmoid(beta) itself and clears the use_beta_sigmoid_in_kernel flag fla-core ignores. When it could not find beta -- a kernel signature that hides it, as a decorator's *args does, or beta passed some other way -- it passed the call on with the flag set, so the kernel ran on the raw gate and the recurrence diverged to inf, a NaN amax after a whole calibration. Such a call is now refused. KimiDynamicCache also gets the get_query_offset shim unless the class defines its own, rather than whenever it can reach one through a base class. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test 99aa5da |
What does this PR do?
Type of change: New feature
Kimi-K3's text backbone,
kimi_linear, has no native transformers modeling to swap in, so the adapter patches the checkpoint's remote code in place:ModuleListwith fused experts that dispatch through the experts interface, keeping K3's latent expert space and its custom SwiGLU.w1/w3intogate_up_projand stackw2intodown_proj. The MXFP4 experts are decompressed as they load ([18/21]).torch_a2a, the experts implementation it names on the text config, with transformers before the plan decision ([5/21]) builds the model on the meta device. transformers refuses to build a model whose experts implementation it has not registered, and the expert-parallel load registers it only afterwards, so before this every multi-rank Kimi-K3 run failed while deciding its plan.A_logshape, and itssigmoid(beta), which fla-core silently skips; real weights then blow the recurrent state up to inf. Remote code whose KDA layers lackchunk_kdaandfused_recurrent_kdato wrap is refused instead of loading with the raw gate, and so is a kernel call whosebetathe wrapper cannot find;generatestopped at its first masked forward with a cache);tie_weights.routed_expert_down_proj/routed_expert_up_proj) and its scalar residual gates (self_attention_res_proj,mlp_res_proj,output_attn_res_proj) unquantized in every built-in format.mm_projectorto the default unit's vision block.--reciperuns, which keep the whole VLM, quantized Kimi's.It needs transformers 5.13, the release it was developed and validated against; its spec declares it, so an older release is refused before
prepareruns. Its spec states the planprepareattaches, as [19/21]'s do, so a run at an EP degree of 1 still finds its experts ([13/21]).The adapter is over the ~500-line budget (~620 lines) because it is a self-contained model drop with no working intermediate state. Every patch is needed before K3 loads expert-parallel at all, and about a third of the lines are the docstrings that record why each patch exists.
Usage
Picked automatically by
apply_distributed_supportforkimi_k3configs undertorchrun; see [21/21].Testing
tests/unit/torch/models/test_kimi_k3_ep.py(+811 lines), against a stand-in remote module that follows the checkpoint's own code, covering:situactivation. The block derives latent MoE fromrouted_expert_hidden_size, as the checkpoint's modeling does; the config has nouse_latent_moefield;tie_weightsshim, including abetapassed positionally and the cache's query offset, added only to a cache without one. The stand-in kernels takeuse_beta_sigmoid_in_kernelthrough**kwargs, as fla-core 0.5's do;preparepatching the defining module once, failing loudly when none defines the MoE block, and refusing a call without the checkpoint's path before it changes anything;**kwargs, and letting any keyword butcache_positionthat the signature rejects raise;prepareattaches equal to the one the spec states;prepareleaving the loader alone for bf16 experts, which now runs withoutcompressed-tensorsinstalled;torch_a2a, passing transformers' experts-implementation check. Without the registration it fails with theexperts_implementation="torch_a2a" is not supportederror that a tiny Kimi-K3 on 2 GPUs (transformers 5.18) hit while deciding its plan;betathe wrapper cannot find (a signature hiding it behind*args) refused rather than run on the raw gate (fails without the fix).tests/unit/torch/utils/test_resolve_distribution_plan.py: a remote-code Kimi-K3 left whole on every rank at an EP degree of 1 is refused, as [19/21]'s Kimi models are.tests/unit/recipe/test_presets.py: the fp8 and nvfp4 presets leave those five projections andmm_projectorunquantized.auto_maploads.betatests into one, themm_projectorpreset into the Kimi projections preset. Each drop is checked by mutation.tests/unit/torch/utils,export,models, the fused-experts quantization tests and the hf_ptq example tests) on transformers 5.18 and 5.5 and on torch 2.8. They pass apart from the diffusers-export and GGML tests, which fail the same way on main in that environment. CI runs the full unit suite.--qformat nvfp4 --kv_cache_qformat fp8, max calibration on 64 samples of the default text blend (sequence length 128, batch 2). It decided its plan, previewed before and after PTQ on both ranks, calibrated in 51 s and exported, in 4 min 28 s at 13.9 GB per GPU. Before this revision it failed while deciding its plan (torch_a2anot registered), and with that fixed, ingenerate(the cache's query offset). The rest of this revision's GPU tests run in CI only.all 92 router correction biases match the checkpoint, to BF16 rounding of its F32 values;
routed experts export as
experts.N.w1/w2/w3and shared experts asgate_proj/up_proj/down_proj, the naming vLLM 0.28's Kimi-K3 loader reads;the latent-expert projections, the residual gates and
mm_projector(373 modules) come out BF16, matching nvidia/Kimi-K3-NVFP4;vLLM 0.28 loads the export and starts serving at TP=32. It cannot serve one with an FP8 KV cache, however:
TBD: generation from the checkpoint-mirror recipe
models/moonshotai/Kimi-K3/ptq/nvfp4_experts-fp8_pb_attention, with [3/21]'s expert weight-scale fix.Before your PR is "Ready for review"
CONTRIBUTING.md: N/A. flash-linear-attention is a runtime requirement of the checkpoint's own remote code, not of ModelOpt.Additional Information
Part 20 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after [19/21].
Summary by CodeRabbit